#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""MBTI 16人格测试核心模块

此模块包含MBTI测试软件的三个核心功能：
1. 题目读取与数据结构处理
2. 维度得分计算与结果匹配
3. PDF报告生成

使用方法：
    from mbti_core import get_questions, calculate_score, match_personality, generate_pdf
"""

import os
import random
import datetime
import math
import pandas as pd
import matplotlib.pyplot as plt
from reportlab.lib.pagesizes import letter
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.lib import colors
from reportlab.platypus import Image as RLImage
import io

# 确保中文显示正常
plt.rcParams["font.family"] = ["SimHei", "WenQuanYi Micro Hei", "Heiti TC"]
plt.rcParams["axes.unicode_minus"] = False  # 解决负号显示问题

# 定义MBTI 16人格类型信息
PERSONALITY_TYPES = {
    'ESTJ': {'name': '监督者', 'traits': ['注重实际', '逻辑思维', '组织能力强', '重视传统'],
             'career': ['管理者', '律师', '军事人员', '会计师'],
             'social': ['重视规则', '可靠', '喜欢明确的社交结构'],
             'learning': ['注重实用性', '喜欢结构化学习', '动手能力强']},
    'ESFJ': {'name': '供给者', 'traits': ['热情友好', '乐于助人', '重视和谐', '善于照顾他人'],
             'career': ['教师', '护士', '社会工作者', '客户服务'],
             'social': ['善于建立关系', '重视家庭', '喜欢参与社区活动'],
             'learning': ['通过互动学习', '注重实际应用', '喜欢合作学习']},
    'ENTJ': {'name': '指挥官', 'traits': ['自信果断', '战略性思维', '目标导向', '善于领导'],
             'career': ['企业高管', '项目经理', '律师', '政治家'],
             'social': ['直接', '有说服力', '喜欢智力挑战'],
             'learning': ['快速学习者', '喜欢理论和概念', '自主学习能力强']},
    'ENFJ': {'name': '教导者', 'traits': ['富有洞察力', '善于沟通', '理想主义', '重视他人成长'],
             'career': ['心理咨询师', '教师', '公共关系专家', '人力资源管理'],
             'social': ['善于倾听', '有同理心', '喜欢帮助他人'],
             'learning': ['通过讨论学习', '重视价值观', '喜欢与他人分享知识']},
    'ISTJ': {'name': '检查者', 'traits': ['严谨细致', '责任心强', '务实可靠', '重视细节'],
             'career': ['会计', '审计师', '工程师', '档案管理员'],
             'social': ['保守', '可靠', '重视长期关系'],
             'learning': ['循序渐进', '注重准确性', '喜欢实践学习']},
    'ISFJ': {'name': '保护者', 'traits': ['温柔体贴', '富有同情心', '认真负责', '善于照顾他人'],
             'career': ['护士', '教师', '行政助理', '社会工作者'],
             'social': ['重视隐私', '忠诚', '喜欢稳定的社交圈'],
             'learning': ['耐心', '注重细节', '喜欢有明确目标的学习']},
    'INTJ': {'name': '建筑师', 'traits': ['富有洞察力', '逻辑思维', '独立思考', '追求卓越'],
             'career': ['科学家', '工程师', '战略顾问', '学术研究者'],
             'social': ['偏好深度交流', '重视隐私', '选择性社交'],
             'learning': ['自主学习', '喜欢理论研究', '注重系统化知识']},
    'INFJ': {'name': '咨询师', 'traits': ['理想主义', '直觉敏锐', '富有创造力', '重视精神层面'],
             'career': ['心理咨询师', '作家', '艺术家', '哲学家'],
             'social': ['深沉', '有同理心', '喜欢有意义的交流'],
             'learning': ['通过反思学习', '重视内在联系', '喜欢探索意义']},
    'ESTP': {'name': '创业者', 'traits': ['乐观开朗', '行动派', '适应性强', '善于解决问题'],
             'career': ['销售人员', '企业家', '运动员', '急救人员'],
             'social': ['外向', '喜欢冒险', '善于即兴发挥'],
             'learning': ['通过实践学习', '喜欢尝试新事物', '注重结果']},
    'ESFP': {'name': '表演者', 'traits': ['热情活泼', '善于表达', '喜欢享受生活', '重视当下'],
             'career': ['演员', '主持人', '营销人员', '旅游从业者'],
             'social': ['友好', '善于娱乐他人', '喜欢社交活动'],
             'learning': ['通过互动学习', '喜欢多样化', '注重趣味性']},
    'ENTP': {'name': '发明家', 'traits': ['好奇心强', '思维敏捷', '善于辩论', '喜欢挑战'],
             'career': ['科学家', '设计师', '市场营销', '律师'],
             'social': ['幽默', '喜欢头脑风暴', '善于打破常规'],
             'learning': ['快速学习', '喜欢探索新领域', '重视创新']},
    'ENFP': {'name': '奋斗者', 'traits': ['热情洋溢', '富有创造力', '理想主义', '善于社交'],
             'career': ['广告创意', '教育培训', '心理咨询', '创业'],
             'social': ['开放', '友好', '喜欢认识新朋友'],
             'learning': ['通过兴趣学习', '喜欢多样化', '重视个人成长']},
    'ISTP': {'name': '手艺者', 'traits': ['冷静理性', '动手能力强', '善于分析', '喜欢冒险'],
             'career': ['工程师', '技术人员', '侦探', '运动员'],
             'social': ['独立', '低调', '喜欢行动胜于言语'],
             'learning': ['通过实践学习', '喜欢解决实际问题', '注重效率']},
    'ISFP': {'name': '创作者', 'traits': ['敏感细腻', '富有艺术气质', '温和善良', '重视个人价值观'],
             'career': ['艺术家', '设计师', '兽医', '社会工作者'],
             'social': ['温和', '尊重他人', '喜欢和谐的环境'],
             'learning': ['通过体验学习', '喜欢个性化', '重视自我表达']},
    'INTP': {'name': '逻辑学家', 'traits': ['理性思维', '好奇心强', '善于分析', '追求知识'],
             'career': ['科学家', '程序员', '数学家', '哲学家'],
             'social': ['喜欢独处', '重视智力交流', '选择性社交'],
             'learning': ['自主学习', '喜欢探索原理', '重视逻辑']},
    'INFP': {'name': '治疗师', 'traits': ['理想主义', '敏感体贴', '富有同情心', '重视内在价值'],
             'career': ['心理咨询师', '作家', '教育工作者', '社会工作者'],
             'social': ['真诚', '重视深度关系', '喜欢有意义的交流'],
             'learning': ['通过兴趣学习', '重视个人价值观', '喜欢内省']}
}

def get_questions(file_path='./data/questions.xlsx', shuffle=True):
    """从Excel文件读取题目并处理
    
     Args:
        file_path (str): Excel文件路径，默认为'./data/questions.xlsx'
        shuffle (bool): 是否随机打乱题目顺序，默认为True
    
    Returns:
        list: 处理后的题目列表，每个元素为字典
    
    Raises:
        FileNotFoundError: 当Excel文件不存在时
        Exception: 当读取或处理文件过程中出现其他错误时
    """
    try:
        # 检查文件是否存在
        if not os.path.exists(file_path):
            # 如果文件不存在，创建默认数据目录和示例文件
            os.makedirs(os.path.dirname(file_path), exist_ok=True)
            create_sample_questions_file(file_path)
            print(f"已创建示例题目文件: {file_path}")
        
        # 使用pandas读取Excel文件
        df = pd.read_excel(file_path)
        
        # 验证Excel表头是否正确
        required_columns = ["题目 ID", "题干", "选项 A", "选项 A 维度", "选项 B", "选项 B 维度", "选项 C", "选项 C 维度", "选项 D", "选项 D 维度"]
        for col in required_columns:
            if col not in df.columns:
                raise ValueError(f"Excel文件缺少必要的列: {col}")
        
        # 将DataFrame转换为题目列表
        questions = []
        for _, row in df.iterrows():
            question = {
                'id': int(row['题目 ID']),
                'text': row['题干'],
                'options': [
                    {'text': row['选项 A'], 'dimension': row['选项 A 维度']},
                    {'text': row['选项 B'], 'dimension': row['选项 B 维度']},
                    {'text': row['选项 C'], 'dimension': row['选项 C 维度']},
                    {'text': row['选项 D'], 'dimension': row['选项 D 维度']}
                ]
            }
            questions.append(question)
        
        # 如果需要随机打乱题目顺序
        if shuffle:
            random.shuffle(questions)
        
        return questions
        
    except FileNotFoundError:
        raise FileNotFoundError(f"题目文件不存在: {file_path}")
    except ValueError as e:
        raise ValueError(str(e))
    except Exception as e:
        raise Exception(f"读取题目文件时出错: {str(e)}")

def create_sample_questions_file(file_path):
    """创建示例题目Excel文件（当文件不存在时）"""
    # 创建示例题目数据
    sample_data = [
        {"题目 ID": 1, "题干": "当你在社交场合中，你更倾向于：", 
         "选项 A": "主动与他人交流并享受其中", "选项 A 维度": "E", 
         "选项 B": "观察他人，在感到舒适时参与交流", "选项 B 维度": "I",
         "选项 C": "视情况而定，有时主动有时被动", "选项 C 维度": "E",
         "选项 D": "倾向于保持安静，倾听他人说话", "选项 D 维度": "I"},
        {"题目 ID": 2, "题干": "在做决定时，你通常更重视：",
         "选项 A": "逻辑分析和客观事实", "选项 A 维度": "T",
         "选项 B": "个人价值观和他人感受", "选项 B 维度": "F",
         "选项 C": "权衡利弊后做出决定", "选项 C 维度": "T",
         "选项 D": "遵循内心的直觉和情感", "选项 D 维度": "F"},
        {"题目 ID": 3, "题干": "你更倾向于哪种工作方式？",
         "选项 A": "有明确的计划和截止日期", "选项 A 维度": "J",
         "选项 B": "灵活安排，根据情况调整", "选项 B 维度": "P",
         "选项 C": "提前规划，按部就班执行", "选项 C 维度": "J",
         "选项 D": "喜欢即兴发挥，随机应变", "选项 D 维度": "P"},
        {"题目 ID": 4, "题干": "当你学习新事物时，你更喜欢：",
         "选项 A": "通过实际操作和体验", "选项 A 维度": "S",
         "选项 B": "通过理论研究和思考", "选项 B 维度": "N",
         "选项 C": "观察他人的做法并模仿", "选项 C 维度": "S",
         "选项 D": "探索概念之间的联系", "选项 D 维度": "N"},
        {"题目 ID": 5, "题干": "在团队合作中，你更倾向于：",
         "选项 A": "积极参与讨论，表达自己的观点", "选项 A 维度": "E",
         "选项 B": "认真倾听他人，在必要时发言", "选项 B 维度": "I",
         "选项 C": "主动承担领导角色，组织大家", "选项 C 维度": "E",
         "选项 D": "专注于自己的任务，默默贡献", "选项 D 维度": "I"}
    ]
    
    # 创建DataFrame并保存为Excel
    df = pd.DataFrame(sample_data)
    df.to_excel(file_path, index=False)

def calculate_score(answers):
    """计算用户答题的各维度得分
    
    Args:
        answers (list): 用户答题列表，每个元素为字典，包含"题目 ID"、"用户选择"、"选择维度"
    
    Returns:
        dict: 包含各维度得分和人格类型的结果字典
    
    Raises:
        ValueError: 当答题列表为空或格式不正确时
    """
    try:
        # 检查答题列表是否为空
        if not answers:
            raise ValueError("答题列表不能为空")
        
        # 初始化四个维度的得分为0
        scores = {'E': 0, 'I': 0, 'S': 0, 'N': 0, 'T': 0, 'F': 0, 'J': 0, 'P': 0}
        
        # 遍历答题列表，累加对应维度得分
        for answer in answers:
            # 验证答案格式是否正确
            if '选择维度' not in answer:
                raise ValueError(f"答案格式不正确，缺少'选择维度'字段: {answer}")
            
            dimension = answer['选择维度']
            # 检查维度是否有效
            if dimension not in scores:
                raise ValueError(f"无效的维度值: {dimension}")
            
            # 累加得分
            scores[dimension] += 1
        
        # 计算每个维度的倾向（得分高的维度，相等时默认第一个）
        e_i = 'E' if scores['E'] > scores['I'] else 'I' if scores['E'] < scores['I'] else 'E'
        s_n = 'S' if scores['S'] > scores['N'] else 'N' if scores['S'] < scores['N'] else 'S'
        t_f = 'T' if scores['T'] > scores['F'] else 'F' if scores['T'] < scores['F'] else 'T'
        j_p = 'J' if scores['J'] > scores['P'] else 'P' if scores['J'] < scores['P'] else 'J'
        
        # 组合四个维度倾向
        personality_type = e_i + s_n + t_f + j_p
        
        # 返回结果
        return {
            'scores': scores,
            'dimensions': {'E/I': e_i, 'S/N': s_n, 'T/F': t_f, 'J/P': j_p},
            'personality_type': personality_type
        }
        
    except ValueError as e:
        raise ValueError(str(e))
    except Exception as e:
        raise Exception(f"计算得分时出错: {str(e)}")

def match_personality(dimensions):
    """根据维度组合匹配人格类型
    
    Args:
        dimensions (str or dict): 维度组合字符串（如"ESTJ"）或包含各维度倾向的字典
    
    Returns:
        dict: 包含人格类型详细信息的字典
    
    Raises:
        ValueError: 当维度组合无效时
    """
    try:
        # 获取人格类型代码
        if isinstance(dimensions, dict):
            # 如果是字典格式，组合维度
            personality_type = dimensions['E/I'] + dimensions['S/N'] + dimensions['T/F'] + dimensions['J/P']
        elif isinstance(dimensions, str) and len(dimensions) == 4:
            # 如果是字符串格式，直接使用
            personality_type = dimensions.upper()
        else:
            raise ValueError(f"无效的维度组合格式: {dimensions}")
        
        # 检查人格类型是否存在
        if personality_type not in PERSONALITY_TYPES:
            raise ValueError(f"无效的人格类型: {personality_type}")
        
        # 获取人格类型详细信息
        personality_info = PERSONALITY_TYPES[personality_type]
        
        # 返回结果
        return {
            'personality_type': personality_type,
            'personality_name': personality_info['name'],
            'traits': personality_info['traits'],
            'career': personality_info['career'],
            'social': personality_info['social'],
            'learning': personality_info['learning']
        }
        
    except ValueError as e:
        raise ValueError(str(e))
    except Exception as e:
        raise Exception(f"匹配人格类型时出错: {str(e)}")

def generate_pdf(result, score_data, save_path=None):
    """生成PDF报告
    
    Args:
        result (dict): 用户测试结果，包含"测试时间"、"人格名称"、"核心特质"、"适配职业"等
        score_data (dict): 各维度得分数据，包含E/I、S/N、T/F、J/P的具体得分
        save_path (str): PDF保存路径，默认为./reports/[测试时间]_人格报告.pdf
    
    Returns:
        str: 生成的PDF文件路径
    
    Raises:
        IOError: 当文件写入权限不足时
        Exception: 当生成PDF过程中出现其他错误时
    """
    try:
        # 设置默认保存路径
        if not save_path:
            # 确保reports目录存在
            os.makedirs('./reports', exist_ok=True)
            # 生成文件名
            test_time = result.get('测试时间', datetime.datetime.now().strftime('%Y%m%d%H%M%S'))
            personality_type = result.get('personality_type', '未知')
            save_path = f'./reports/{test_time}_{personality_type}_人格报告.pdf'
        
        # 生成雷达图
        radar_chart_buffer = generate_radar_chart(score_data)
        
        # 创建PDF文档
        doc = SimpleDocTemplate(save_path, pagesize=letter)
        elements = []
        
        # 设置样式
        styles = getSampleStyleSheet()
        
        # 添加封面
        title = Paragraph("<h1>MBTI人格测试软件</h1>", styles['Title'])
        elements.append(title)
        elements.append(Paragraph("<h2>人格测试报告</h2>", styles['Heading1']))
        elements.append(Spacer(1, 48))
        
        # 添加测试信息
        test_info = Paragraph(
            f"<b>测试时间：</b>{result.get('测试时间', datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'))}<br/>" \
            f"<b>人格类型：</b>{result.get('personality_type', '未知')} - {result.get('personality_name', '未知')}", 
            styles['BodyText']
        )
        elements.append(test_info)
        elements.append(Spacer(1, 48))
        
        # 人格类型卡片（居中显示）
        personality_card = Paragraph(
            f"<center><font size=24>{result.get('personality_name', '未知')}</font></center>" \
            f"<center><font size=36>{result.get('personality_type', '未知')}</font></center>",
            styles['BodyText']
        )
        elements.append(personality_card)
        elements.append(Spacer(1, 48))
        
        # 添加维度得分表
        elements.append(Paragraph("<h2>维度得分表</h2>", styles['Heading2']))
        
        # 计算总题数和各维度占比
        total_questions = sum(score_data.values()) // 4  # 每个问题贡献一个维度得分
        
        # 准备表格数据
        data = [
            ['维度', '得分', '倾向', '占比'],
            ['外倾(E) / 内倾(I)', f"{score_data.get('E', 0)} / {score_data.get('I', 0)}", 
             result.get('dimensions', {}).get('E/I', '未知'), 
             f"{score_data.get('E', 0)}/{total_questions}"],
            ['感觉(S) / 直觉(N)', f"{score_data.get('S', 0)} / {score_data.get('N', 0)}", 
             result.get('dimensions', {}).get('S/N', '未知'), 
             f"{score_data.get('S', 0)}/{total_questions}"],
            ['思考(T) / 情感(F)', f"{score_data.get('T', 0)} / {score_data.get('F', 0)}", 
             result.get('dimensions', {}).get('T/F', '未知'), 
             f"{score_data.get('T', 0)}/{total_questions}"],
            ['判断(J) / 感知(P)', f"{score_data.get('J', 0)} / {score_data.get('P', 0)}", 
             result.get('dimensions', {}).get('J/P', '未知'), 
             f"{score_data.get('J', 0)}/{total_questions}"],
        ]
        
        # 创建表格
        table = Table(data)
        table.setStyle(TableStyle([
            ('BACKGROUND', (0, 0), (-1, 0), (0.8, 0.8, 0.8)),
            ('TEXTCOLOR', (0, 0), (-1, 0), (0, 0, 0)),
            ('ALIGN', (0, 0), (-1, -1), 'CENTER'),
            ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
            ('BOTTOMPADDING', (0, 0), (-1, 0), 12),
            ('GRID', (0, 0), (-1, -1), 1, (0, 0, 0))
        ]))
        
        elements.append(table)
        elements.append(Spacer(1, 24))
        
        # 添加维度雷达图
        elements.append(Paragraph("<h2>维度雷达图</h2>", styles['Heading2']))
        elements.append(RLImage(radar_chart_buffer, width=400, height=300))
        elements.append(Spacer(1, 24))
        
        # 添加核心特质解读
        elements.append(Paragraph("<h2>核心特质解读</h2>", styles['Heading2']))
        traits = result.get('traits', ['暂无数据'])
        for trait in traits[:5]:  # 限制显示3-5条
            elements.append(Paragraph(f"• {trait}", styles['BodyText']))
        elements.append(Spacer(1, 12))
        
        # 添加适配职业推荐
        elements.append(Paragraph("<h2>适配职业推荐</h2>", styles['Heading2']))
        careers = result.get('career', ['暂无数据'])
        for career in careers:
            elements.append(Paragraph(f"• {career}", styles['BodyText']))
        
        # 构建PDF
        doc.build(elements)
        
        return save_path
        
    except IOError as e:
        raise IOError(f"文件写入失败，请检查权限: {str(e)}")
    except Exception as e:
        raise Exception(f"生成PDF报告时出错: {str(e)}")

def generate_radar_chart(score_data):
    """生成维度雷达图
    
    Args:
        score_data (dict): 各维度得分数据
    
    Returns:
        io.BytesIO: 雷达图的内存缓冲区
    """
    # 准备数据
    labels = ['外倾(E)', '内倾(I)', '感觉(S)', '直觉(N)', '思考(T)', '情感(F)', '判断(J)', '感知(P)']
    stats = [score_data.get('E', 0), score_data.get('I', 0), score_data.get('S', 0), 
             score_data.get('N', 0), score_data.get('T', 0), score_data.get('F', 0), 
             score_data.get('J', 0), score_data.get('P', 0)]
    
    # 计算角度
    angles = [n / float(len(labels)) * 2 * math.pi for n in range(len(labels))]
    stats = stats + stats[:1]  # 闭合雷达图
    angles = angles + angles[:1]
    
    # 创建图表
    fig, ax = plt.subplots(figsize=(8, 8), subplot_kw=dict(polar=True))
    ax.plot(angles, stats, 'o-', linewidth=2, color='#165DFF')
    ax.fill(angles, stats, alpha=0.25, color='#165DFF')
    ax.set_thetagrids([a * 180 / math.pi for a in angles[:-1]], labels)
    ax.set_title('MBTI维度得分雷达图')
    
    # 将图表保存到内存缓冲区
    buffer = io.BytesIO()
    plt.savefig(buffer, format='png')
    buffer.seek(0)
    plt.close(fig)
    
    return buffer

# 示例使用方法
if __name__ == '__main__':
    try:
        # 1. 读取题目示例
        print("1. 读取题目示例：")
        questions = get_questions()
        print(f"成功读取{len(questions)}道题目\n")
        
        # 2. 计算得分和匹配人格类型示例
        print("2. 计算得分和匹配人格类型示例：")
        # 模拟用户答案
        sample_answers = [
            {'题目 ID': 1, '用户选择': 'A', '选择维度': 'E'},
            {'题目 ID': 2, '用户选择': 'A', '选择维度': 'T'},
            {'题目 ID': 3, '用户选择': 'A', '选择维度': 'J'},
            {'题目 ID': 4, '用户选择': 'B', '选择维度': 'N'},
            {'题目 ID': 5, '用户选择': 'A', '选择维度': 'E'}
        ]
        
        # 计算得分
        score_result = calculate_score(sample_answers)
        print(f"维度得分: {score_result['scores']}")
        print(f"维度倾向: {score_result['dimensions']}")
        print(f"人格类型: {score_result['personality_type']}")
        
        # 匹配人格类型
        personality_info = match_personality(score_result['personality_type'])
        print(f"人格名称: {personality_info['personality_name']}")
        print(f"核心特质: {personality_info['traits']}\n")
        
        # 3. 生成PDF报告示例
        print("3. 生成PDF报告示例：")
        # 准备结果数据
        result_data = {
            '测试时间': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
            **score_result,
            **personality_info
        }
        
        # 生成PDF
        pdf_path = generate_pdf(result_data, score_result['scores'])
        print(f"PDF报告已生成: {pdf_path}")
        
    except Exception as e:
        print(f"示例运行出错: {str(e)}")